/*
 * Syntelos 'SX'
 * Copyright (C) 1998, 2007  John Pritchard.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */
package syntelos.sx;

import alto.io.Input;
import alto.io.Output;
import alto.io.Principal;
import alto.io.Uri;
import alto.io.u.Chbuf;
import alto.lang.Address;
import alto.lang.Component;
import alto.lang.ConsumptionInputStream;
import alto.lang.Date;
import alto.lang.Header;
import alto.lang.HttpBodyUrlEncoded;
import alto.lang.InputStream;
import alto.lang.Protocol;
import alto.lang.Status;
import alto.lang.Type;
import alto.lang.Value;
import alto.sys.FileManager;
import alto.sys.IO;
import alto.sys.Reference;

/**
 * Server read request. 
 * 
 * <h3>Authentication</h3>
 * 
 * Note that Method programmers write 
 * 
 * <pre>
 * if (this.isAuthenticated()){
 *   // (ok)
 * }
 * </pre>
 * 
 * and <i>not</i>
 * 
 * <pre>
 * if (req.isAuthenticated()){
 *   // (!)
 * }
 * </pre>
 * 
 * 
 * @author jdp
 * @since 1.6
 */
public final class Request 
    extends syntelos.lang.HttpRequest
    implements alto.lang.Closeable,
               IO.Location.Transient,
               IO.Source.Fio
{

    public final static Reference ReferenceToIndex(Request request){
        return Type.Tools.Of("idx").referenceTo(request);
    }

    public final static java.lang.Class RESPONSE_CLASS = Response.class;

    /**
     * Reads will timeout in four seconds.
     */
    public final static int SO_TIMEOUT_INSECURE = 4000;
    /**
     * Reads will timeout in eight seconds.
     */
    public final static int SO_TIMEOUT_SECURE = 6000;

    public final static boolean IsPathUAddressing(char u){
        switch (u){
        case '0':
            return true;
        case '1':
            return true;
        case '2':
            return true;
        case '3':
            return true;
        case '4':
            return true;
        case '5':
            return true;
        case '6':
            return true;
        case '7':
            return true;
        case '8':
            return true;
        case '9':
            return true;
        case 'a':
            return true;
        case 'b':
            return true;
        case 'c':
            return true;
        case 'd':
            return true;
        case 'e':
            return true;
        case 'f':
            return true;
        default:
            return false;
        }
    }
    public final static boolean IsNotPathUAddressing(char u){
        switch (u){
        case '0':
            return false;
        case '1':
            return false;
        case '2':
            return false;
        case '3':
            return false;
        case '4':
            return false;
        case '5':
            return false;
        case '6':
            return false;
        case '7':
            return false;
        case '8':
            return false;
        case '9':
            return false;
        case 'a':
            return false;
        case 'b':
            return false;
        case 'c':
            return false;
        case 'd':
            return false;
        case 'e':
            return false;
        case 'f':
            return false;
        default:
            return true;
        }
    }
    public final static int PATHU_LEN = 3;
    public final static char PATHU_NIL = (char)0;
    /**
     * <p> Lowercase 'a' - 'z' reserved names map to hard coded global
     * system applications.  The first example of such an application
     * is 'u', as in the special request path
     * <code>"/u/32342e6c6f071403/431609145a097472/5952"</code>. </p>
     * 
     * @param u A lowercase ASCII character, 'a' through 'z' inclusive.
     * 
     * @return Null or a global application package name, eg,
     * <code>"u"</code> maps to <code>"syntelos.sx.u"</code>.
     */
    public final static String PkgPathU(char u){
        switch (u){
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
            return "syntelos.sx.methods.u";

        case 'g':
            return "syntelos.sx.methods.g";
        case 'h':
            return "syntelos.sx.methods.h";
        case 'i':
            return "syntelos.sx.methods.i";
        case 'j':
            return "syntelos.sx.methods.j";
        case 'k':
            return "syntelos.sx.methods.k";
        case 'l':
            return "syntelos.sx.methods.l";
        case 'm':
            return "syntelos.sx.methods.m";
        case 'n':
            return "syntelos.sx.methods.n";
        case 'o':
            return "syntelos.sx.methods.o";
        case 'p':
            return "syntelos.sx.methods.p";
        case 'q':
            return "syntelos.sx.methods.q";
        case 'r':
            return "syntelos.sx.methods.r";
        case 's':
            return "syntelos.sx.methods.s";
        case 't':
            return "syntelos.sx.methods.t";
        case 'u':
            return "syntelos.sx.methods.u";
        case 'v':
            return "syntelos.sx.methods.v";
        case 'w':
            return "syntelos.sx.methods.w";
        case 'x':
            return "syntelos.sx.methods.x";
        case 'y':
            return "syntelos.sx.methods.y";
        case 'z':
            return "syntelos.sx.methods.z";
        default:
            return null;
        }
    }
    /**
     * 
     */
    public final static String RSchemePathU(char u){
        switch (u){
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case 'a':
        case 'b':
        case 'c':
        case 'd':
        case 'e':
        case 'f':
            return "store";
        case 'g':
        case 'h':
        case 'i':
        case 'j':
        case 'k':
        case 'l':
        case 'm':
        case 'n':
        case 'o':
        case 'p':
        case 'q':
        case 'r':
        case 's':
            return "store";
        case 't':
            return "thread";
        case 'u':
        case 'v':
        case 'w':
        case 'x':
        case 'y':
        case 'z':
        default:
            return "store";
        }
    }
    public final static boolean IsPathU(String pathS){
        return (PATHU_NIL != PathU(pathS));
    }
    public final static char PathU(String pathS){
        return PathU(pathS.toCharArray());
    }
    /**
     * For all paths '/[a-z]' or '/[a-z]/*', return char in '[a-z]'.
     * Otherwise value zero.
     * 
     * @return Value zero for not "pathu".  Otherwise a character
     * value (in range 'a' - 'z' inclusive) for {@link PathU(char)}.
     */
    public final static char PathU(char[] path){
        int pathlen = path.length;
        if (2 <= pathlen && '/' == path[0]){
            if (2 == pathlen ||  '/' == path[2]){
                char ch = path[1];
                if ('a' <= ch && 'z' >= ch)
                    return ch;
            }
        }
        return PATHU_NIL;
    }
    public final static String PkgNotPathU(Request request){
        String path = Component.Tools.Clean(request.getPath());
        if (null == path)
            return null;
        else if (null == request.pathType)
            return path.replace('/','.');
        else {
            int idx = path.lastIndexOf('/');
            if (0 < idx)
                return path.substring(0,idx).replace('/','.');
            else
                return null;
        }
    }
    public final static String MethodQName(Request request){
        String mname = request.getMethodCamel();
        String pkg = request.getPkg();
        if (null != pkg)
            return pkg+'.'+mname;
        else
            return mname;
    }
    /**
     * Fio ops GET, PUT, DELETE
     */
    public final static boolean IsMethodFio(String method){
        switch (method.length()){
        case 3:
            switch (method.charAt(0)){
            case 'G':
                return (method.endsWith("ET"));
            case 'P':
                return (method.endsWith("UT"));
            default:
                return false;
            }
        case 6:
            return ("DELETE".equals(method));
        default:
            return false;
        }
    }
    /**
     */
    public final static boolean HasFioType(Type pathType, Request request){
        if (null != pathType && pathType.hasFio())
            return true;
        else {
            Type.List fioTypes = request.getFioTypes();
            return (null != fioTypes && 0 < fioTypes.size());
        }
    }
    public final static boolean HasFioFragment(Request request){
        return (null != request.getFioFragment());
    }
    public final static String Location(Request request){
        String host = request.getHost();
        if (null == host){
            int port = request.service_port;
            java.net.InetAddress service = request.service_ipaddr;
            try {
                host = service.getHostName();
            }
            catch (Exception exc){
                host = service.getHostAddress();
            }
            if (80 != port)
                host += (":"+port);
        }
        String resource = request.getPath();
        if ('/' == resource.charAt(0))
            return request.getScheme()+"://"+host+resource;
        else
            return request.getScheme()+"://"+host+'/'+resource;
    }
    public final static String Location(Request request, String path){
        String host = request.getHost();
        if (null == host){
            int port = request.service_port;
            java.net.InetAddress service = request.service_ipaddr;
            try {
                host = service.getHostName();
            }
            catch (Exception exc){
                host = service.getHostAddress();
            }
            if (80 != port)
                host += (":"+port);
        }
        if ('/' == path.charAt(0))
            return request.getScheme()+"://"+host+path;
        else
            return request.getScheme()+"://"+host+'/'+path;
    }


    private Server server;
    private String scheme;
    private java.net.InetAddress client_ipaddr;
    private java.net.InetAddress service_ipaddr;
    private int service_port;

    private String methodCamel;
    private String hostname;
    private Uri resource;
    private Reference reference_rlog;

    private String requestLocation;
    private char pathu;

    private InputStream in;
    private long time_enter;
    private String pkg;
    private Reference reference;
    private Type pathType;
    private boolean fio;
    private String fioFragment;
    private Type.List queryTypes, fioTypes;
    private byte[] trace;
    private boolean traceRead;


    public Request(Socket client)
        throws java.io.IOException
    {
        super(client);
    }


    public boolean isRequestClient(){
        return false;
    }
    public boolean isRequestServer(){
        return true;
    }
    protected final void setSocket(Socket socket)
        throws java.io.IOException
    {
        if (socket.isSecure())
            socket.setSoTimeout(SO_TIMEOUT_SECURE);
        else
            socket.setSoTimeout(SO_TIMEOUT_INSECURE);
        this.socket = socket;
    }
    protected final void messageRead()
        throws java.io.IOException
    {
        try {
            this.messageReadRequest();
        }
        catch (java.io.IOException exc){
            throw new Ignore(exc);
        }
    }
    protected final void messageReadPre()
        throws java.io.IOException
    {
        this.time_enter = System.currentTimeMillis();

        Socket client = this.getClientSX();
        Server server = client.getServer();
        this.server = server;
        this.scheme = server.getProtocolScheme();

        this.client_ipaddr = client.getInetAddress();
        this.service_ipaddr = client.getLocalAddress();
        this.service_port = client.getLocalPort();

        this.in = (InputStream)this.socket.getInput();
    }
    protected final void messageReadRequestPost()
        throws java.io.IOException
    {
        super.messageReadRequestPost();

        String hostname = this.getHostName();

        Component reference_rlog = this.getServerSX().requestSequence();

        if (null == hostname){
            this.hostname = null;
            /*
             */
            this.reference_rlog = new syntelos.sys.Reference(Component.Host.Local,Type.Tools.Of("sx-log-http"),reference_rlog);
        }
        else {
            this.hostname = hostname;
            /*
             */
            this.reference_rlog = new syntelos.sys.Reference(hostname,Type.Tools.Of("sx-log-http"),reference_rlog);
        }

        this.methodCamel = Response.Camel(this.method);

        /*
         * As soon as hostname has been assigned, setup the Context
         * Class Loader.
         */
        FileManager application = syntelos.sys.FileManager.Instance(this);

        java.lang.Thread.currentThread().setContextClassLoader(application);

        String path = this.pathComplete;

        this.pathu = PathU(path);

        String rscheme = RSchemePathU(this.pathu);

        if (null != hostname){
            String locationBasic = Chbuf.cat(rscheme,"://",Chbuf.fcat(hostname,path));
            Uri resource = new alto.io.u.Uri(locationBasic);
            if (WwwForm.TYPE == this.getContentType())
                this.resource = new WwwForm(this, resource);
            else
                this.resource = resource;
        }
        else {
            String locationBasic = Chbuf.cat(rscheme,":",path);
            Uri resource = new alto.io.u.Uri(locationBasic);
            //
            if (WwwForm.TYPE == this.getContentType())
                this.resource = new WwwForm(this, resource);
            else
                this.resource = resource;
        }
        /*
         */
        this.setReference(this.resource);

        this.requestLocation = Location(this);

        /*
         */
        if (PATHU_NIL != this.pathu)
            this.pkg = PkgPathU(this.pathu);
        else
            this.pkg = PkgNotPathU(this);
        /*
         * Uses Context Class Loader
         */
        if (IsMethodFio(this.method) &&
            HasFioFragment(this) &&
            HasFioType(this.pathType,this))
        {
            this.fio = true;
            this.pkg += ".fragment";
        }
    }

    /**
     * Called from {@link Service} to instantiate a new instance of
     * {@link Response}.
     */
    public Response respond()
        throws java.io.IOException,
               java.lang.InterruptedException
    {
        Output out = this.socket.openOutput();
        Response rep = this.createResponse();
        rep.init(this,out);
        rep.respond(this);
        this.consume();
        return rep;
    }
    protected Response createResponse(){
        FileManager fm = FileManager.Instance();

        String mqnameWithPath = this.getMethodQName();

        if (this.isNotPathU()){
            /*
             * mqname is not fully qualified 
             * 
             * use path...
             */
            String mqname2 = fm.packageName(mqnameWithPath);
            Response response = (Response)fm.create(mqnameWithPath,RESPONSE_CLASS);
            if (null != response)
                return response;

            else {
                /*
                 * ignore path...
                 */
                String mname = this.getMethodCamel();
                if (mname != mqnameWithPath){
                    String mqnameWithoutPath = fm.packageName(mname);

                    response = (Response)fm.create(mqnameWithoutPath,RESPONSE_CLASS);
                    if (null != response)
                        return response;
                }
            }
        }
        else {
            /*
             * mqname is fully qualified 
             */
            Response response = (Response)fm.create(mqnameWithPath,RESPONSE_CLASS);
            if (null != response)
                return response;
        }
        /*
         * default methods
         */
        String mqname;
        if (this.fio){
            mqname = "syntelos.sx.methods.fragment."+this.getMethodCamel();
        }
        else {
            mqname = "syntelos.sx.methods."+this.getMethodCamel();
        }
        Response response = (Response)fm.create(mqname,RESPONSE_CLASS);
        if (null != response)
            return response;
        else {
            /*
             */
            return Response.NotImplemented().cloneResponse();
        }
    }
//     protected final void serverAuthenticate()
//         throws java.io.IOException
//     {
//         /*
//          * first call from constructor prepares process
//          */
//         this.serverAuthenticateSAuth();
//     }
//     public void authenticate()
//         throws java.io.IOException
//     {
//         /*
//          * second call completes process
//          */
//         this.serverAuthenticateSAuth();
//     }
    public void consume(){
        InputStream in = this.in;
        if (null != in){
            try {
                in.consume();
            }
            catch (Exception ignore){
            }
        }
    }
    public boolean isTraceread(){
        return this.in.isTraceread();
    }
    public void traceread(){
        this.in.traceread();
    }
    public void traceread(boolean enable){
        this.in.traceread(enable);
    }
    public void enterTraceread(){
        this.in.enterTraceread();
    }
    public void exitTraceread(){
        this.in.exitTraceread();
    }
    public String getName(){
        return this.hostname;
    }
    public boolean isOutputLocation(){
        return true;
    }
    public String getScheme(){
        return this.scheme;
    }
    public String getHostName(){
        return this.hostname;
    }
    public boolean isPersistentRequest(){
        if (Protocol.HTTP11 == this.protocol){
            String test = this.getHeaderString("Connection");
            if (null == test)
                return true;
            else {
                return "keep-alive".equalsIgnoreCase(test);
            }
        }
        else
            return false;
    }
    public String getRequestLocation(){
        return this.requestLocation;
    }
    /**
     * Called by {@link #redirectWelcome(syntelos.sx.Response)}. 
     * 
     * @return Welcome filename for application and request path,
     * never null but may not exist.  Default
     * <code>"index.html"</code>.
     */
    public final String getWelcomeFor(Request request){
        /*
         * TODO  Use a per directory welcome-meta with directory parent
         * TODO  inheritance and (Accept+User-Agent) resolution.
         * TODO  Looking for a (web/svg/mobile) solution. 
         */
        return "index.html";
    }
    /**
     * @return Request is "GET .../" for possible "GET .../index.html"
     * welcome redirect.
     */
    public boolean canRedirectWelcome(){
        if (this.hasNotType()){
            String path = this.getPath();
            return ('/' == path.charAt(path.length()-1));
        }
        else
            return false;
    }
    public void redirectWelcome(Response rep)
        throws java.io.IOException
    {
        String path = this.getPath();
        String welcome = this.getWelcomeFor(this);
        path = Chbuf.fcat(path,welcome);
        rep.redirectToPath(path);
    }
    public void redirectDir(Response rep)
        throws java.io.IOException
    {
        String path = this.getPath()+'/';
        rep.redirectToPath(path);
    }
    public Reference getReferenceLog(){
        return this.reference_rlog;
    }
    public java.lang.String getReferenceLogRequestPath(){
        return this.reference_rlog.getRequestPath();
    }
    public boolean hasReference(){
        return (null != this.reference);
    }
    public boolean hasNotReference(){
        return (null == this.reference);
    }
    public Reference getReference(){
        return this.reference;
    }
    public void setReference(Uri parser)
        throws java.io.IOException
    {
        try {
            this.setReference(new syntelos.sys.Reference(parser).toLocal());
        }
        catch (java.lang.RuntimeException exc){
            this.reference = null;
            this.pathType = Type.Tools.Of(parser.getPathTail());
        }
    }
    public void setReference(Reference reference)
        throws java.io.IOException
    {
        if (null != reference){
            this.reference = reference;
            this.pathType = reference.getPathNameType();
        }
        else
            throw new IllegalArgumentException();
    }
    public Address getReferenceAddress(){
        Reference reference = this.reference;
        if (null != reference)
            return reference.getAddress();
        else
            return null;
    }
    public Type getReferencePathNameType(){
        Reference reference = this.reference;
        if (null != reference)
            return reference.getPathNameType();
        else
            return null;
    }
    public java.lang.String getReferenceRequestPath(){
        Reference reference = this.reference;
        if (null != reference)
            return reference.getRequestPath();
        else
            return null;
    }
    public Reference getReferenceResponseNotFound()
        throws java.io.IOException
    {
        return this.getReferenceResponseForBasePath("/404");
    }
    public Reference getReferenceResponseConflict()
        throws java.io.IOException
    {
        return this.getReferenceResponseForBasePath("/409");
    }
    public Reference getReferenceResponseBadRequest()
        throws java.io.IOException
    {
        return this.getReferenceResponseForBasePath("/400");
    }
    public Reference getReferenceResponseUnauthorized()
        throws java.io.IOException
    {
        return this.getReferenceResponseForBasePath("/401");
    }
    public Reference getReferenceResponseForbidden()
        throws java.io.IOException
    {
        return this.getReferenceResponseForBasePath("/403");
    }
    public Reference getReferenceResponseError()
        throws java.io.IOException
    {
        return this.getReferenceResponseForBasePath("/500");
    }
    public Reference getReferenceResponseForBasePath(String bp)
        throws java.io.IOException
    {
        Reference reference;
        Type type = this.resolveType();
        if (type.hasFext()){
            java.lang.String fext = type.getFext();
            java.lang.String path = bp+'.'+fext;
            reference = new Reference(this.hostname,type,path);
        }
        else {
            type = Type.Tools.Of("html");
            java.lang.String path = bp+".html";
            reference = new Reference(this.hostname,type,path);
        }
        return reference;
    }

    public boolean isFile(){
        Reference reference = this.reference;
        if (null != reference)
            return (reference.isStorageFile());
        else
            return false;
    }
    public long getLength(){
        Reference reference = this.reference;
        if (null != reference)
            return reference.getStorageLength();
        else
            return 0L;
    }
    public long getLastModified(){
        long date = super.getLastModified();
        if (0L < date)
            return date;
        else if (this.hasReference()){
            return this.getReference().getLastModified();
        }
        else
            return 0L;
    }
    public String getLastModifiedString(){
        String string = super.getLastModifiedString();
        if (null != string)
            return string;
        else if (this.hasReference()){
            return this.getReference().getLastModifiedString();
        }
        else
            return null;
    }
    public Server getServerSX(){
        Server server = this.server;
        if (null == server){
            Socket socket = this.getClientSX();
            if (null == socket)
                return null;
            else {
                server = socket.getServer();
                this.server = server;
            }
        }
        return server;
    }
    public Socket getClientSX(){
        return (Socket)this.socket;
    }
    /**
     * @return Connection employs a protected transport
     * @see #isSecurePrivate()
     */
    public boolean isSecure(){
        return this.socket.isSecure();
    }
    /**
     * @return Connection employs a protected transport which is
     * providing privacy.
     */
    public boolean isSecurePrivate(){
        return this.socket.isSecurePrivate();
    }
    /**
     * @see #isSecure()
     * @exception syntelos.sys.Error$Bug For insecure socket
     */
    public javax.net.ssl.SSLSession getSecureSession(){
        return this.socket.getSecureSession();
    }
    public java.security.cert.Certificate[] getSecureRemoteCertificates(){
        return this.socket.getSecureRemoteCertificates();
    }
    /**
     * @return May be null for SSL Peer Unverified 
     * @see #isSecure()
     */
    public java.security.Principal getSecureRemote(){
        return this.socket.getSecureRemote();
    }
    /**
     * @return Username may be null, never throws an exception.
     */
    public java.lang.String getSecureRemoteUsername(){
        try {
            java.security.Principal identity = this.getSecureRemote();
            if (null != identity)
                return identity.getName();
        }
        catch (Exception exc){
            /*
             * TODO (temp)
             */
            java.io.PrintStream err = java.lang.System.err;
            synchronized(err){
                err.print(java.lang.Thread.currentThread().getName()+' ');
                exc.printStackTrace(err);
            }
        }
        return null;
    }
    /**
     * @return Never null, otherwise Bug 
     * @see #isSecure()
     */
    public java.security.Principal getSecureHost(){
        return this.socket.getSecureHost();
    }
    public String getMethodCamel(){
        return this.methodCamel;
    }
    public String getMethodQName(){

        return MethodQName(this);
    }
    public boolean isPathUAddressing(){
        return IsPathUAddressing(this.pathu);
    }
    public boolean isNotPathUAddressing(){
        return IsNotPathUAddressing(this.pathu);
    }
    public boolean isPathU(){
        return (PATHU_NIL != this.pathu);
    }
    public boolean isNotPathU(){
        return (PATHU_NIL == this.pathu);
    }
    public char getPathU(){
        return this.pathu;
    }
    public String getPath(){
        return this.resource.getPath();
    }
    public String getPathParent(){
        return this.resource.getPathParent();
    }
    public String getPathHead(){
        return this.resource.getPathHead();
    }
    public String getPathTail(){
        return this.resource.getPathTail();
    }
    public String getIntern(){
        return this.resource.getIntern();
    }
    public String getTerminal(){
        return this.resource.getTerminal();
    }
    public String getFragment(){
        return this.resource.getFragment();
    }
    public Uri getUri(){
        return this.resource;
    }
    public HttpBodyUrlEncoded getHttpBodyUrlEncoded(){
        Uri resource = this.resource;
        if (resource instanceof HttpBodyUrlEncoded)
            return (HttpBodyUrlEncoded)resource;
        else
            return null;
    }
    public java.lang.String getFioFragment(){
        java.lang.String fioFragment = this.fioFragment;
        if (null == fioFragment){
            Uri resource = this.resource;
            fioFragment = resource.getFragment();
            if (null != fioFragment)
                this.fioFragment = fioFragment;
            else {
                fioFragment = resource.getQuery("fragment");
                if (null != fioFragment)
                    this.fioFragment = fioFragment;
            }
        }
        return fioFragment;
    }
    public Uri getFioReference(){
        if (null == this.reference)
            return this.resource;
        else {
            Type.List fioTypes = this.fioTypes;
            if (null != fioTypes && 0 < fioTypes.size())
                return this.resource;
            else
                return this.reference;
        }
    }
    public Type.List getFioTypes(){
        Type.List fioTypes = this.fioTypes;
        if (null == fioTypes){
            Type.List queryTypes = this.getQueryTypes();
            if (null != queryTypes){
                fioTypes = new Type.List();
                for (int cc = 0, count = queryTypes.size(); cc < count; cc++){
                    Type type = queryTypes.get(cc);
                    if (null != type && type.hasFio())
                        fioTypes.add(type);
                }
                this.fioTypes = fioTypes;
            }
        }
        return fioTypes;
    }
    public Type.List getQueryTypes(){
        Type.List queryTypes = this.queryTypes;
        if (null == queryTypes){
            String types = this.resource.getQuery("type");
            if (null != types){
                queryTypes = FileManager.List.Type(types);
                this.queryTypes = queryTypes;
            }
        }
        return queryTypes;
    }
    public boolean hasType(){
        return (null != this.pathType);
    }
    public boolean hasNotType(){
        return (null == this.pathType);
    }
    public Type getType(){
        return this.pathType;
    }
    public void setType(Type type){
        if (null != type)
            this.pathType = type;
        else
            throw new IllegalArgumentException();
    }
    /**
     * @return Never null, defined type (for request path), reference
     * meta defined type, or default type text plain.
     * @see #getReference()
     */
    public Type resolveType(){
        Type type = this.pathType;
        if (null != type)
            return type;
        else if (this.hasReference()){
            try {
                type = this.getReference().getContentType();
                if (null != type)
                    return type;
            }
            catch (java.io.IOException ignore){
            }
        }
        return Type.Tools.Of("txt");
    }
    public String getPkg(){
        return this.pkg;
    }
    public byte[] getTrace(){
        return this.trace;
    }
    public long getTraceLength(){
        byte[] trace = this.trace;
        if (null != trace)
            return trace.length;
        else
            return 0;
    }
    public long getTimeEnter(){
        return this.time_enter;
    }
    /**
     * Available after {@link #close}.
     */
    public long getTransactionLengthRequest(){
        if (this.traceRead)
            return this.getTraceLength();
        else
            return this.getTraceLength()+this.getContentLength();
    }
    public java.net.InetAddress getClientAddress(){
        return this.client_ipaddr;
    }
    public java.net.InetAddress getServiceAddress(){
        return this.service_ipaddr;
    }
    public int getServicePort(){
        return this.service_port;
    }
    /**
     * Called from {@link Response} from {@link Service}.  Should never
     * throw any exception.
     */
    public void close(){
        this.consume();
        InputStream in = this.in;
        if (null != in){
            this.in = null;
            this.trace = in.getTrace();
            this.traceRead = in.isTraceread();
        }
    }
    public org.w3c.dom.Document readDocument()
        throws org.w3c.dom.DOMException, org.w3c.dom.ls.LSException
    {
        long len = this.getContentLength();
        if (0L < len){
            try {
                java.io.InputStream in = this.getInputStream();
                return this.readDocument(STDIN,in);
            }
            catch (java.io.IOException exc){
                throw new alto.sys.Error.Bug(exc);
            }
        }
        else
            throw new org.w3c.dom.ls.LSException(org.w3c.dom.ls.LSException.PARSE_ERR,"No Content-Length.");
    }
    public long lastModified(){
        Reference reference = this.reference;
        if (null != reference)
            return reference.getStorageLastModified();
        else
            return 0L;
    }
    public java.lang.String lastModifiedString(){
        Reference reference = this.reference;
        if (null != reference)
            return reference.getStorageLastModifiedString();
        else
            return null;
    }
    public boolean setLastModified(long len){
        Reference reference = this.reference;
        if (null != reference)
            return reference.setLastModified(len);
        else
            return false;
    }
    public java.nio.channels.WritableByteChannel openChannelWritable(){
        return null;
    }
    public java.nio.channels.WritableByteChannel getChannelWritable(){
        return null;
    }
    public java.io.OutputStream openOutputStream()
        throws java.io.IOException
    {
        return null;
    }
    public alto.io.Output openOutput()
        throws java.io.IOException
    {
        return null;
    }
    public java.io.OutputStream getOutputStream()
        throws java.io.IOException
    {
        return null;
    }
    public alto.io.Output getOutput()
        throws java.io.IOException
    {
        return null;
    }
    public Reference[] list()
        throws java.io.IOException
    {
        FileManager fm = FileManager.Instance();
        if (this.hasReference()){
            return fm.list(this.reference);
        }
        else {
            return fm.list(this.resource);
        }
    }
    public Address[] listVersions()
        throws java.io.IOException
    {
        FileManager fm = FileManager.Instance();
        if (this.hasReference())
            return fm.listVersions(this.reference);
        else 
            return null;
    }
}
